package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Fxz
 * @version 1.0
 * @date 2022-02-12 16:13
 */
@SuppressWarnings("all")
@Slf4j
@Service
@RequiredArgsConstructor
public class SearchServiceImpl implements SearchService {

	private final RedisTemplate redisTemplate;

	private final GoodsRepository goodsRepository;

	private final RestHighLevelClient restHighLevelClient;

	private final ProductFeignClient productFeignClient;

	/**
	 * 商品上架
	 */
	@Override
	public void upperGoods(Long skuId) {
		// sku信息
		SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
		Optional.ofNullable(skuInfo).ifPresent((sku) -> {
			// 品牌信息
			BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
			// 分类信息
			BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
			// 平台属性
			List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
			List<SearchAttr> searchAttrList = null;
			if (CollectionUtils.isNotEmpty(attrList)) {
				searchAttrList = attrList.stream().map(attr -> {
					SearchAttr searchAttr = new SearchAttr();
					if (ObjectUtils.isNotEmpty(attr)) {
						searchAttr.setAttrId(attr.getId());
						searchAttr.setAttrName(attr.getAttrName());
						searchAttr.setAttrValue(CollectionUtils.isNotEmpty(attr.getAttrValueList())
								? attr.getAttrValueList().get(0).getValueName() : "");
					}
					return searchAttr;
				}).collect(Collectors.toList());
			}

			Goods goods = Goods.builder().id(skuInfo.getId()).defaultImg(skuInfo.getSkuDefaultImg())
					.title(skuInfo.getSkuName())
					.price(skuInfo.getPrice() != null ? skuInfo.getPrice().doubleValue() : 0).createTime(new Date())
					.tmId(skuInfo.getTmId()).tmName(ObjectUtils.isNotEmpty(trademark) ? trademark.getTmName() : "")
					.tmLogoUrl(ObjectUtils.isNotEmpty(trademark) ? trademark.getLogoUrl() : "")
					.category1Id(ObjectUtils.isNotEmpty(categoryView) ? categoryView.getCategory1Id() : -1)
					.category2Id(ObjectUtils.isNotEmpty(categoryView) ? categoryView.getCategory2Id() : -1)
					.category3Id(ObjectUtils.isNotEmpty(categoryView) ? categoryView.getCategory3Id() : -1)
					.category1Name(ObjectUtils.isNotEmpty(categoryView) ? categoryView.getCategory1Name() : "")
					.category2Name(ObjectUtils.isNotEmpty(categoryView) ? categoryView.getCategory2Name() : "")
					.category3Name(ObjectUtils.isNotEmpty(categoryView) ? categoryView.getCategory3Name() : "")
					.attrs(searchAttrList).build();
			goodsRepository.save(goods);
		});
	}

	/**
	 * 商品下架
	 */
	@Override
	public void lowerGoods(Long skuId) {
		goodsRepository.deleteById(skuId);
	}

	/**
	 * 更新热度值
	 */
	@Override
	public void incrHotScore(Long skuId) {
		// redis中zset的key
		String hotScoreKey = "hotScore";
		// redis中热度加一
		Double score = redisTemplate.opsForZSet().incrementScore(hotScoreKey, "skuId:" + skuId, 1);

		log.info("商品:{} 热度更新为:{}", skuId, score);

		// 更新es
		if (score % 10 == 0) {
			Goods goods = goodsRepository.findById(skuId).orElse(Goods.builder().id(skuId).build());
			goods.setHotScore(score.longValue());
			goodsRepository.save(goods);
			log.info("商品:{} 更新es热度为:{}", skuId, score);
		}
	}

	/**
	 * 检索数据
	 * @param searchParam 检索条件
	 */
	@SneakyThrows
	@Override
	public SearchResponseVo search(SearchParam searchParam) {
		// 跟据用户检索条件生成查询DSL语句
		SearchRequest searchRequest = this.buildQueryDsl(searchParam);
		// 执行DSL语句并获取结果集
		SearchResponse searchResponse = this.restHighLevelClient.search(searchRequest);
		// 将查询出来的结果集进行封装
		SearchResponseVo responseVo = this.parseSearchResult(searchResponse);
		// 当前页
		responseVo.setPageNo(searchParam.getPageNo());
		// 每页最大记录数
		responseVo.setPageSize(searchParam.getPageSize());
		// 总页数
		responseVo.setTotalPages(
				responseVo.getTotal() % responseVo.getPageSize() == 0 ? responseVo.getTotal() / responseVo.getPageSize()
						: (responseVo.getTotal() / responseVo.getPageSize() + 1));
		return responseVo;
	}

	/**
	 * 查询数据结果集的转换
	 */
	private SearchResponseVo parseSearchResult(SearchResponse searchResponse) {
		SearchHits hits = searchResponse.getHits();
		SearchHit[] hitsHits = hits.getHits();

		SearchResponseVo responseVo = new SearchResponseVo();

		// 设置总条数
		responseVo.setTotal(hits.getTotalHits());

		// 设置GoolsList
		if (ArrayUtils.isNotEmpty(hitsHits)) {
			List<Goods> goodsList = Arrays.stream(hitsHits).map(item -> {
				// JSON格式 此时title没有高亮
				String sourceAsString = item.getSourceAsString();
				// 转为java对象
				Goods goods = JSON.parseObject(sourceAsString, Goods.class);
				// 获取高亮数据
				HighlightField highlightField = item.getHighlightFields().get("title");
				Optional.ofNullable(highlightField).ifPresent((field) -> {
					Text[] fragments = field.getFragments();
					Text title = fragments[0];
					goods.setTitle(title.string());
				});
				return goods;
			}).collect(Collectors.toList());
			// 设置GoodList
			responseVo.setGoodsList(goodsList);
		}

		// 获取品牌数据
		Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();

		ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
		List<SearchResponseTmVo> tmVoList = tmIdAgg.getBuckets().stream().map(bucket -> {
			SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
			// 品牌id
			String keyAsString = bucket.getKeyAsString();
			searchResponseTmVo.setTmId(Long.valueOf(keyAsString));
			// 品牌名称
			ParsedStringTerms tmNameAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("tmNameAgg");
			if (ObjectUtils.isNotEmpty(tmNameAgg) && CollectionUtils.isNotEmpty(tmNameAgg.getBuckets())) {
				String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
				searchResponseTmVo.setTmName(tmName);
			}
			// 品牌Url
			ParsedStringTerms tmLogUrlAgg = (ParsedStringTerms) bucket.getAggregations().asMap().get("tmLogUrlAgg");
			if (ObjectUtils.isNotEmpty(tmLogUrlAgg) && CollectionUtils.isNotEmpty(tmLogUrlAgg.getBuckets())) {
				String tmLogUrl = tmLogUrlAgg.getBuckets().get(0).getKeyAsString();
				searchResponseTmVo.setTmLogoUrl(tmLogUrl);
			}
			return searchResponseTmVo;
		}).collect(Collectors.toList());
		responseVo.setTrademarkList(tmVoList);

		// 获取平台属性
		ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
		ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
		List<SearchResponseAttrVo> attrVoList = new LinkedList<>();
		if (ObjectUtils.isNotEmpty(attrIdAgg) && CollectionUtils.isNotEmpty(attrIdAgg.getBuckets())) {
			for (int i = 0; i < attrIdAgg.getBuckets().size(); i++) {
				SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
				Number keyAsNumber = attrIdAgg.getBuckets().get(i).getKeyAsNumber();
				searchResponseAttrVo.setAttrId(keyAsNumber.longValue());
				ParsedStringTerms attrNameAgg = attrIdAgg.getBuckets().get(i).getAggregations().get("attrNameAgg");
				searchResponseAttrVo.setAttrName(
						ObjectUtils.isNotEmpty(attrNameAgg) && CollectionUtils.isNotEmpty(attrNameAgg.getBuckets())
								? attrNameAgg.getBuckets().get(0).getKeyAsString() : "");
				ParsedStringTerms attrValueAgg = attrIdAgg.getBuckets().get(i).getAggregations().get("attrValueAgg");
				if (ObjectUtils.isNotEmpty(attrValueAgg) && CollectionUtils.isNotEmpty(attrValueAgg.getBuckets())) {
					List<String> attrsList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString)
							.collect(Collectors.toList());
					searchResponseAttrVo.setAttrValueList(attrsList);
				}
				attrVoList.add(searchResponseAttrVo);
			}
		}
		responseVo.setAttrsList(attrVoList);

		return responseVo;
	}

	/**
	 * 生成dsl语句
	 * https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html
	 */
	private SearchRequest buildQueryDsl(SearchParam searchParam) {
		SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
		BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

		// 判断用户是否跟据分类id进行查询
		if (ObjectUtils.isNotEmpty(searchParam.getCategory1Id())) {
			// query---bool---filter---term
			boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id()));
		}
		if (ObjectUtils.isNotEmpty(searchParam.getCategory2Id())) {
			// query---bool---filter---term
			boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id()));
		}
		if (ObjectUtils.isNotEmpty(searchParam.getCategory3Id())) {
			// query---bool---filter---term
			boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
		}

		// 判断用户是否跟据keyWord检索
		if (StringUtils.isNotBlank(searchParam.getKeyword())) {
			// query---bool---must---match
			boolQueryBuilder.must(QueryBuilders.matchQuery("title", searchParam.getKeyword()).operator(Operator.AND));
		}

		// 判断是否跟据品牌id过滤
		if (StringUtils.isNotBlank(searchParam.getTrademark())) {
			// 前端传的格式 1:华为
			String trademark = searchParam.getTrademark();
			String[] strings = trademark.split(":");
			boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", strings[0]));
		}

		// 判断用户是否跟据平台属性值过滤
		String[] props = searchParam.getProps();
		if (ArrayUtils.isNotEmpty(props)) {
			Arrays.stream(props).forEach(prop -> {
				String[] strings = prop.split(":");
				if (ArrayUtils.isNotEmpty(strings) && strings.length == 3) {
					BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
					BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();

					subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", strings[0]));
					subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", strings[1]));

					// bool---must---nested
					boolQuery.must(QueryBuilders.nestedQuery("attrs", subBoolQuery, ScoreMode.None));

					boolQueryBuilder.filter(boolQuery);
				}
			});
		}

		searchSourceBuilder.query(boolQueryBuilder);

		// 分页
		searchSourceBuilder.from((searchParam.getPageNo() - 1) * searchParam.getPageSize());
		searchSourceBuilder.size(searchParam.getPageSize());

		// 1表示综合热度排序 2表示价格排序 asc生效 desc降序
		String order = searchParam.getOrder();
		if (StringUtils.isNotBlank(order)) {
			String[] strings = order.split(":");
			if (ArrayUtils.isNotEmpty(strings) && strings.length == 2) {
				String field = "";
				switch (strings[0]) {
				case "1":
					field = "hotScore";
					break;
				case "2":
					field = "price";
					break;
				}
				searchSourceBuilder.sort(field, "asc".equals(strings[1]) ? SortOrder.ASC : SortOrder.DESC);
			}
		}
		else {
			searchSourceBuilder.sort("hotScore", SortOrder.DESC);
		}

		// 高亮
		HighlightBuilder highlightBuilder = new HighlightBuilder();
		highlightBuilder.field("title");
		highlightBuilder.preTags("<span style=color:red>");
		highlightBuilder.postTags("</span>");
		searchSourceBuilder.highlighter(highlightBuilder);

		// 品牌聚合
		TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("tmIdAgg").field("tmId")
				.subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
				.subAggregation(AggregationBuilders.terms("tmLogUrlAgg").field("tmLogoUrl"));
		searchSourceBuilder.aggregation(aggregationBuilder);
		// 平台属性聚合
		searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg", "attrs")
				.subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
						.subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
						.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));

		// 设置显示的字段
		searchSourceBuilder.fetchSource(new String[] { "id", "title", "defaultImg", "price" }, null);

		log.info("DSL:{}", searchSourceBuilder.toString());

		SearchRequest searchRequest = new SearchRequest("goods");
		searchRequest.types("info");
		searchRequest.source(searchSourceBuilder);

		return searchRequest;
	}

}
